/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.client;

import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Random;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import locusts.Main;
import locusts.common.AbstractUniqueObject;

/**
 * Class to hold configuration properties for a client. It is shared with the
 * server, which can chamge anything the client sets.
 * 
 * @author Hamish Morgan
 */
public class ClientConfig extends AbstractUniqueObject implements Serializable,
        Cloneable {

    private transient EventListenerList listenerList;
    private transient ChangeEvent changeEvent = null;
    private transient AffineTransform transform = null;
    //
    private String remoteHost;
    private int remotePort;
    private String localHost;
    private int localPort;
    //
    private double xTranslate;
    private double yTranslate;
    private double rotate;
    private double scale;
    private int width;
    private int height;
    //
    private int modCount;
    //
    private boolean touchEnabled;

    public ClientConfig() {
        super((new Random()).nextInt());
        listenerList = new EventListenerList();
        remoteHost = Main.DEFAULT_HOST;
        remotePort = Main.DEFAULT_PORT;
        localHost = "";
        localPort = -1;
        xTranslate = 0;
        yTranslate = 0;
        rotate = 0;
        scale = 1;
        width = 800;
        height = 600;
        modCount = 0;
        touchEnabled = true;
    }

    protected ClientConfig(ClientConfig that) {
        super(that);
        this.listenerList = new EventListenerList();
        this.height = that.height;
        this.width = that.width;
        this.remotePort = that.remotePort;
        this.remoteHost = that.remoteHost;
        this.localHost = that.localHost;
        this.localPort = that.localPort;
        this.xTranslate = that.xTranslate;
        this.yTranslate = that.yTranslate;
        this.rotate = that.rotate;
        this.scale = that.scale;
        this.modCount = that.modCount;
        this.touchEnabled = that.touchEnabled;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeDouble(xTranslate);
        out.writeDouble(yTranslate);
        out.writeDouble(rotate);
        out.writeDouble(scale);
        out.writeInt(width);
        out.writeInt(height);
        out.writeInt(modCount);
        out.writeObject(localHost);
        out.writeInt(localPort);
        out.writeObject(remoteHost);
        out.writeInt(remotePort);
        out.writeBoolean(touchEnabled);
    }

    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {

        this.xTranslate = in.readDouble();
        this.yTranslate = in.readDouble();
        this.rotate = in.readDouble();
        this.scale = in.readDouble();
        this.width = in.readInt();
        this.height = in.readInt();
        this.modCount = in.readInt();
        this.localHost = (String) in.readObject();
        this.localPort = in.readInt();
        this.remoteHost = (String) in.readObject();
        this.remotePort = in.readInt();
        this.touchEnabled = in.readBoolean();
//        remoteHost = "";
//        remotePort = -1;
        listenerList = new EventListenerList();
        changeEvent = null;
        transform = null;
    }

    public int getModCount() {
        return modCount;
    }

    public void set(ClientConfig that) {
        if (modCount >= that.modCount)
            return;

        boolean changed = false;
        if (this.height != that.height) {
            this.height = that.height;
            changed = true;
        }
        if (this.width != that.width) {
            this.width = that.width;
            changed = true;
        }
        if (this.rotate != that.rotate) {
            this.rotate = that.rotate;
            changed = true;
        }
        if (this.scale != that.scale) {
            this.scale = that.scale;
            changed = true;
        }
        if (this.xTranslate != that.xTranslate) {
            this.xTranslate = that.xTranslate;
            changed = true;
        }
        if (this.yTranslate != that.yTranslate) {
            this.yTranslate = that.yTranslate;
            changed = true;
        }
        if (this.remotePort != that.remotePort) {
            this.remotePort = that.remotePort;
            changed = true;
        }
        if (!this.remoteHost.equals(that.remoteHost)) {
            this.remoteHost = that.remoteHost;
            changed = true;
        }
        if (this.localPort != that.localPort) {
            this.localPort = that.localPort;
            changed = true;
        }
        if (!this.localHost.equals(that.localHost)) {
            this.localHost = that.localHost;
            changed = true;
        }
        if (this.touchEnabled != that.touchEnabled) {
            this.touchEnabled = that.touchEnabled;
            changed = true;
        }

        if (changed) {
            modCount = that.modCount;
            clearCache();
            fireStateChangeEvent();
        }
    }

    public AffineTransform getTransform() {
        if (transform == null) {
            transform = new AffineTransform();
            transform.translate(xTranslate, yTranslate);
            transform.rotate(rotate);
            transform.scale(scale, scale);
        }
        return (AffineTransform) transform.clone();
    }

    private void clearCache() {
        transform = null;
    }

    public String getLocalHost() {
        return localHost;
    }

    public int getLocalPort() {
        return localPort;
    }

    public String getRemoteHost() {
        return remoteHost;
    }

    public int getRemotePort() {
        return remotePort;
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

    public double getRotate() {
        return rotate;
    }

    public double getScale() {
        return scale;
    }

    public double getXTranslate() {
        return xTranslate;
    }

    public double getYTranslate() {
        return yTranslate;
    }

    public boolean isTouchEnabled() {
        return touchEnabled;
    }

    public void setLocalHost(String host) {
        if (!this.localHost.equals(host)) {
            this.localHost = host;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setLocalPort(int port) {
        if (this.localPort != port) {
            this.localPort = port;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setRemotePort(int port) {
        if (this.remotePort != port) {
            this.remotePort = port;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setRemoteHost(String host) {
        if (!this.remoteHost.equals(host)) {
            this.remoteHost = host;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setHeight(int height) {
        if (this.height != height) {
            this.height = height;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setWidth(int width) {
        if (this.width != width) {
            this.width = width;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setSize(int width, int height) {
        if (this.width != width || this.height != height) {
            this.width = width;
            this.height = height;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setRotate(double rotate) {
        if (this.rotate != rotate) {
            this.rotate = rotate;
            clearCache();
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setScale(double scale) {
        if (this.scale != scale) {
            this.scale = scale;
            clearCache();
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setXTranslate(double xTranslate) {
        if (this.xTranslate != xTranslate) {
            this.xTranslate = xTranslate;
            clearCache();
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setYTranslate(double yTranslate) {
        if (this.yTranslate != yTranslate) {
            this.yTranslate = yTranslate;
            clearCache();
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setTouchEnabled(boolean touchEnabled) {
        if (this.touchEnabled != touchEnabled) {
            this.touchEnabled = touchEnabled;
            modCount++;
            fireStateChangeEvent();
        }
    }

    public void setTranslate(double x, double y) {
        if (this.xTranslate != x || this.yTranslate != y) {
            this.xTranslate = x;
            this.yTranslate = y;
            clearCache();
            modCount++;
            fireStateChangeEvent();
        }
    }

    @Override
    public String toString() {
        return localHost + ":" + localPort;
    }

    public void fireStateChangeEvent() {

        final Object source = this;

        final Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ChangeListener.class) {
                if (changeEvent == null)
                    changeEvent = new ChangeEvent(source);
                ((ChangeListener) listeners[i + 1]).stateChanged(
                        changeEvent);
            }
        }
    }

    public void addChangeListener(ChangeListener listener) {
        listenerList.add(ChangeListener.class, listener);
    }

    public void removeChangeListener(ChangeListener listener) {
        listenerList.remove(ChangeListener.class, listener);
    }

    public ChangeListener[] getChangeListeners() {
        return listenerList.getListeners(ChangeListener.class);
    }

    @Override
    public ClientConfig clone() {
        return new ClientConfig(this);
    }
}
